﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using SaunaServer.Model.Logger;
using System.Net;
using System.Net.Security;
using System.Security.Cryptography.X509Certificates;
using System.IO;
using System.Net.Sockets;
using System.Threading;
using MySql.Data.MySqlClient;
using SaunaServer.Model.Database;
using SaunaServer.Controller.Communication;

namespace SaunaServer.Model.Network
{
    /// <summary>
    /// TCP/IP DAO Communication Class
    /// </summary>
    /// <dates>
    /// 28/09/2011 - created by Chiaki Sato
    /// </dates>
    /// <remarks>
    /// Note:
    /// This class is thread safe. 
    /// </remarks>
    public class TcpDaoGatewayServer : TcpBaseSetting, ITcpDaoGatewayServer
    {
        // Constant
        private const string PROTOCOL_PREFIX = "$SAU";
        private const string PROTOCOL_LOGIN_REQUEST = "LI";
        private const string PROTOCOL_LOGIN_RESPONSE = "LR";
        private const string PROTOCOL_LOGIN_RESULT_OK = "OK";
        private const string PROTOCOL_LOGIN_RESULT_BAD = "BAD";
        private const string PROTOCOL_DOWNLOAD_LOG = "LF";
        private const string PROTOCOL_DELIMITER = ",";
        private const char PROTOCOL_DELIMITER_CHAR = ',';
        private const int PROTOCOL_COMMAND_LEVEL1 = 0;
        private const int PROTOCOL_COMMAND_LEVEL2 = 1;
        private const int PROTOCOL_COMMAND_LEVEL3 = 2;
        private const int PROTOCOL_COMMAND_LEVEL4 = 3;
        private const int PROTOCOL_COMMAND_LEVEL5 = 4;
        private const string FILE_LOGGER = "FileLogger";
        private const string MESSAGE_TCP_RECEIVE_LOGIN = "TCP Receive: User ID: {0}, Password: {1}";
        private const string MESSAGE_TCP_SEND = "TCP Send: {0}";
        private const string MESSAGE_TCP_RECEIVE = "TCP Receive: {0}";
        private const string ERROR_MESSAGE_TCP_CLIENT_DISCONNECTED = "Client exits this connection. ";
        private const string MESSAGE_TCP_CLIENT_CONNECTED = "Remote Client {0} is connected.";
       
        // Private variables
        private object lockObject;
        private LoggerFactoryImplementation loggerFactory;
        private ILogger fileLogger;
        private TcpListener listener;
        private bool isContinueListen = true;
        private bool isContinueClient = true;
        private static CommunicationManager communicationManager;

        // Private static singleton
        private static TcpDaoGatewayServer tcpDaoGatewaySslSingleton = new TcpDaoGatewayServer();

        // Public enumeration
        public enum LoginResult { SUCCESS, USER_REJECTED, PROTOCOL_ERROR }

        /// <summary>
        /// Private constructor for singleton
        /// </summary>
        private TcpDaoGatewayServer()
        {
            lockObject = new object();
        }

        ~TcpDaoGatewayServer()
        {
        }

        /// <summary>
        /// Get singleton instance
        /// </summary>
        public static TcpDaoGatewayServer Instance(CommunicationManager comManager, NetworkSetting networkSetting)
        {
            // Initialize Tcp setting parameter
            tcpDaoGatewaySslSingleton.HostAddress = networkSetting.TcpHostAddress;
            tcpDaoGatewaySslSingleton.PortNumber = networkSetting.TcpPortNumber;
            tcpDaoGatewaySslSingleton.UseSsl = networkSetting.TcpSsl;
            tcpDaoGatewaySslSingleton.LogFileDirectory = networkSetting.LogFileDirectory;
            tcpDaoGatewaySslSingleton.loggerFactory = new LoggerFactoryImplementation();
            tcpDaoGatewaySslSingleton.fileLogger = tcpDaoGatewaySslSingleton.loggerFactory.Make(FILE_LOGGER);

            communicationManager = comManager;
            return tcpDaoGatewaySslSingleton;
        }

        public void ListenCommunication()
        {
            IPAddress localhost = IPAddress.Parse(tcpDaoGatewaySslSingleton.HostAddress);

            listener = new System.Net.Sockets.TcpListener(localhost, tcpDaoGatewaySslSingleton.PortNumber);
            listener.Start();

            while (isContinueListen)
            {
                try
                {
                    TcpClient remoteClient = listener.AcceptTcpClient();

                    // Remote client connected
                    Thread threadSession = new Thread(new ParameterizedThreadStart(HandleClientThread));
                    threadSession.IsBackground = true;
                    threadSession.Start(remoteClient);

                    // Check if remote client is still connected
                    if (remoteClient.Connected == true)
                    {
                        // Display tcp log on monitor
                        communicationManager.WriteMonitorMessage(String.Format(MESSAGE_TCP_CLIENT_CONNECTED, remoteClient.Client.RemoteEndPoint));

                        // Record tcp log to file logger
                        fileLogger.Write(String.Format(MESSAGE_TCP_CLIENT_CONNECTED, remoteClient.Client.RemoteEndPoint));
                    }
                }
                catch (SocketException e)
                {
                    // listener.Stop() cause SocketException
                    Console.WriteLine(e);
                }
            }
        }

        public void EndCommunication()
        {
            isContinueClient = false;
            isContinueListen = false;
            if (listener != null)
            {
                listener.Stop();
            }
        }

        private void HandleClientThread(object obj)
        {
            TcpClient tcpClient = obj as TcpClient; ;
            String receiveData = "";

            try
            {
                while (isContinueClient)
                {
                    receiveData = ReceiveMessage(obj);
                    ExamineReceivedRequestData(tcpClient, receiveData);
                }
            }
            catch (IOException e)
            {
                // Client exits this connection
                Console.WriteLine(ERROR_MESSAGE_TCP_CLIENT_DISCONNECTED + e);
                fileLogger.Write(ERROR_MESSAGE_TCP_CLIENT_DISCONNECTED);
            }
            finally
            {
                tcpClient.Close();
            }
        }

        /// <summary>
        /// Receive data from client
        /// <param name="obj">TCP client hander</param>
        /// </summary>
        private String ReceiveMessage(object obj)
        {
            TcpClient tcpClient = obj as TcpClient; ;
            String receiveData = "";
            byte[] buffer = new byte[256];
            int totalRead = 0;
            try
            {
                do
                {
                    int readCnt = tcpClient.GetStream().Read(buffer, totalRead, buffer.Length - totalRead);
                    totalRead += readCnt;

                } while (tcpClient.GetStream().DataAvailable);

                receiveData = Encoding.Unicode.GetString(buffer, 0, totalRead);

                // Display tcp log on monitor
                communicationManager.WriteMonitorMessage(String.Format(MESSAGE_TCP_RECEIVE, receiveData));

                // Record tcp log to file logger
                fileLogger.Write(String.Format(MESSAGE_TCP_RECEIVE, receiveData));

                return receiveData;
            }
            catch (IOException e)
            {
                throw e;
            }
        }

        /// <summary>
        /// Validate Login Response data
        /// </summary>
        /// <param name="receiveData">Received response data from server</param>
        /// <returns>Tcp Transfer Status</returns>
        private CommunicationStatus ExamineReceivedRequestData(TcpClient tcpClient, String receiveData)
        {
            string[] protocolCommand;

            protocolCommand = receiveData.Split(new char[] { PROTOCOL_DELIMITER_CHAR });

            if (protocolCommand[PROTOCOL_COMMAND_LEVEL1] != PROTOCOL_PREFIX)
            {
                return new CommunicationStatus(CommunicationStatus.Status.PROTOCOL_ERROR);
            }

            switch (protocolCommand[PROTOCOL_COMMAND_LEVEL2])
            {
                case PROTOCOL_LOGIN_REQUEST:
                    return Login(tcpClient, protocolCommand);

                case PROTOCOL_DOWNLOAD_LOG:
                    return CreateDownloadLog(tcpClient, protocolCommand);

                default:
                    return new CommunicationStatus(CommunicationStatus.Status.PROTOCOL_ERROR);
            }
        }

        private CommunicationStatus Login(TcpClient tcpClient, string[] protocolCommand)
        {
            string userId = protocolCommand[PROTOCOL_COMMAND_LEVEL3];
            string password = protocolCommand[PROTOCOL_COMMAND_LEVEL4];

            Console.WriteLine(String.Format(MESSAGE_TCP_RECEIVE_LOGIN, userId, password));

            string userFullName = "";
            bool isSuccess = communicationManager.AuthenticateUser(userId, password, out userFullName);
            if (isSuccess)
            {
                SendLoginResponseToClient(tcpClient, LoginResult.SUCCESS);
                CommunicationStatus userAuthenticationstatus = new CommunicationStatus(CommunicationStatus.Status.SUCCESS);
                userAuthenticationstatus.UserFullName = userFullName;
                return userAuthenticationstatus;
            }
            else
            {
                SendLoginResponseToClient(tcpClient, LoginResult.USER_REJECTED);
                return new CommunicationStatus(CommunicationStatus.Status.USER_REJECTED);
            }
        }

        private CommunicationStatus CreateDownloadLog(TcpClient tcpClient, string[] protocolCommand)
        {
            string userId = protocolCommand[PROTOCOL_COMMAND_LEVEL3];
            string fileName = protocolCommand[PROTOCOL_COMMAND_LEVEL4];
            string dateTime = protocolCommand[PROTOCOL_COMMAND_LEVEL5];

            communicationManager.CreateDownloadLogRecord(userId, fileName, dateTime);
            CommunicationStatus userAuthenticationstatus = new CommunicationStatus(CommunicationStatus.Status.SUCCESS);
            return userAuthenticationstatus;
        }

        /// <summary>
        /// Send login response to client
        /// <param name="tcpClient">TCP client hander</param>
        /// <param name="loginResult">Login Result</param>
        /// </summary>
        public void SendLoginResponseToClient(TcpClient tcpClient, LoginResult loginResult)
        {
            if (loginResult == LoginResult.SUCCESS)
            {
                SendDataToServer(tcpClient, PROTOCOL_PREFIX + PROTOCOL_DELIMITER + PROTOCOL_LOGIN_RESPONSE
                               + PROTOCOL_DELIMITER + PROTOCOL_LOGIN_RESULT_OK);
            }
            else
            {
                SendDataToServer(tcpClient, PROTOCOL_PREFIX + PROTOCOL_DELIMITER + PROTOCOL_LOGIN_RESPONSE
                               + PROTOCOL_DELIMITER + PROTOCOL_LOGIN_RESULT_BAD);
            }
        }

        /// <summary>
        /// Send data to server by TCP/IP
        /// </summary>
        /// <param name="sendData">Send data to server</param>
        private void SendDataToServer(TcpClient tcpClient, string sendData)
        {
            byte[] bytes = Encoding.Unicode.GetBytes(sendData);
            tcpClient.GetStream().Write(bytes, 0, bytes.Length);

            // Display tcp log on monitor
            communicationManager.WriteMonitorMessage(String.Format(MESSAGE_TCP_SEND, sendData));

            // Record tcp log to file logger
            fileLogger.Write(String.Format(MESSAGE_TCP_SEND, sendData));
        }
    
    }
}
